#!/usr/bin/python
# coding=utf8
###############################################################################
#                                                                             #
#    Copyright 2007 Danil Dotsenko                                            #
#                                                                             #
#    This program is free software; you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, version 2 of the License.                  #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program; if not, write to the Free Software              #
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA #
#                                                                             #
###############################################################################                                   #
# Contacts: Danil Dotsenko, dd@accentsolution.com

try:
	from karamba import *
except:
	pass
from time import time

import kdehelper
kdehelper = kdehelper.helper()

class threader(object):
	'''
	This is a thread-farm class.
	How it works:
	- farm = [modulename].threadfarm()
	- pass a function and arguments like this. farm.add(fn, arg)
	- after you are done, you MUST kill the the farm by farm.stop()
	  otherwise the python interpreter will never let go.
	the thread-farms automatically assigns the task to available thread.
	
	AGAIN!!!! YOU MUST obj.STOP() THE FARM BEFORE DELETING THE OBJECT.
	(if someone can suggest how to override __del__() with thread-killing goodness, let me know)
	
	This is turned off by default and processes all commands is serial fasion.
	'''
	def __init__(self):
		'''
		threads - number of threads what the farm will be running.
		If no number given, we fake threading. Useful for simple
		applications and where threding issues can cause problems.
		'''
		self.threads = 0
		self.thfarm = []
		# self.start(3) # <------ uncomment this line to activate threading support.
		# note, you have to find a way to stop the threads when your applet exits.
		# otherwise, you will have to "killall superkaramba" as your only option.

	def start(self, threads):
		if self.thfarm:
			self.stop()
		if threads:
			import Queue
			import threading
			self.threads = threads
			self.thfarm = []
			self.q = Queue.Queue()
			for n in range(threads):
				self.thfarm.append(threading.Thread(target=self.__worker__))
				self.thfarm[n].start()

	def stop(self):
		self.flag = False
		for n in self.thfarm:
			self.q.put('stop')
		self.threads = 0

	def __delete__(self):
		self.stop()

	def __worker__(self):
		'''
		This will be run in each thread. 
		We are constantly looking at the queue (and just spend time waiting.)
		
		Think about timing out .get(), testing time-stamp on while and
		putting threads to sleep / waking up with demand variation.
		
		Until then, please, please dont forget to farm.stop() before exiting.
		'''
		while self.threads:
			try:
				args = self.q.get(True, 4)
			except:
				args = None
			if args:
				if args[0] == 'stop':
					break
				else:
					fn = args[0]
					kw = args[args.__len__()-1] # last item in args was kw folded in by add()
					args = args.__getslice__(1, args.__len__())
					fn(*args, **kw)

	def add(self, *args, **kw):
		if self.threads:
			args = args.append(kw)
			self.q.put((args))
		elif args and args != 'stop':
			# this means we dont run thread farm.
			# blocking until done.
			fn = args[0]
			args = args.__getslice__(1, args.__len__())
			#print "add thread, kw ", args, kw, fn
			fn(*args, **kw)


###############################
# This is a little trick to make the variables from this module available in
# modules that import * from this module. 
# once the class bellow is instantiated, it will keep the settings 
# available for all threds and all wrapping layers of the code.
# see example usage in helper.py that "import *" this code.
class globalcfg:
	# this is the only (known to me) way to make it available to all upper-wrapping modules
	cfg = {
		'clickables':{},
		'hoverables':{},
		'hoverstack':[],
		'hoverbusy':[False],
		'threadfarm':threader(),
		}
# but, the class itself cannot be read directly, so we need a wrapping function.
def getcfg(item=None):
	if item:
		try:
			ret = globalcfg.cfg[item]
		except:
			globalcfg.cfg[item] = {}
			ret = globalcfg.cfg[item]
	else:
		ret = globalcfg.cfg
	return ret
#
#################################

#################################
# We are using a neat trick in karambino when it comes to redrawing the applet.
# This is due to a bug in standard karamba.redrawWidget(widget)
# sometimes redrawWidget fails, while widgetUpdated() never fails to redraw.
# For this reason we hijack the interval timer to bring us to widgetUpdated()
# when we need that redraw.
# Bellow, we set the interval to Zero, and in the widgetUpdated() we set it back to normal.
# DO NOT FORGET THIS. IN widgetUpdated() ADD FUNCTION TO RETURN INTERVAL TO NORMAL, or you get 100% CPU usage.
redrawstatus = True

def redraw(widget, **kw):
	global redrawstatus
	try:
		status = kw['redraw']
	except:
		status = redrawstatus
	if status:
		changeInterval(widget, 0)

# Device independance magic.
	'''
	All the numbers we throw around here by default are not pixels, but are "dots"
	Now, what the hell is a dot?

	A dot is an object with 0.254 millimeter side on a 100dpi display device.

	Now, again, what the hell is a dot!!!???!!!

	Ok, ok... this is our way of simulating and defining scaling user interface through use
	of "indexed" measure units. If you had a monitor with an exactly 100dpi,
	OUR "dot" will equal your screen pixel, which would measure exactly 0.254 mm or 0.01 inch if
	you measured it with a ruller.

	Every time you pass a number to the move/size functions, you give them under assumption
	that they are pixels in that 100dpi fictional universe.

	This library converts those perfect, fictional units into proportionally
	accurate amount of actual pixels for your imperfect, real monitor.

	The idea here is to have the following:
	If you defined that a font is 12 POINTS
	(which should be exactly 0.17 inch, or 4.23 mm, or 16.67 "dots" tall on any media)
	than it will be exactly 12 POINTS.
	
	If you defined that a frame around a text is 33 "dots" tall (24 POINTS, or 2x height of 12 Point font)
	than it will be 2x times the 12 point font.

	Why bother with this? Because the growth of hi-dpi monitors reqires us to do so.
	'''
def toscreen(value, **kw):
	'''
	takes 'dots' and converts them into amount of pixels appropriate for your monitor

	actual pixels = actual dpi converted into rate from 100dpi and multiplied into 'dot' standard unit
	'''
	# dot = 1 # standard unit, number of pixels one 'dot' occupies in 100dpi world
	# therefore, there is no reason put it in the formula
	return value
	#try:
		#units = kw['units']
		## you can pass "px" through the function to signal your are passing actual pixels
	#except:
		## but by defalt we work with dots
		#units = 'dot'
	#if units == 'dot':
		#x,y,dpi=kdehelper.screeninfo()
		#return int(round(value*dpi*0.01))
	#else:
		#return value

def fromscreen(value, **kw):
	'''
	takes actual screen pixels and converts them into "dots"
	actual pixels = actual dpi converted into rate from 100dpi and multiplied into 'dot' standard unit
	'''
	# dot = 1 # standard unit, number of pixels one 'dot' occupies in 100dpi world
	# therefore, there is no reason put it in the formula
	return value
	#try:
		#units = kw['units']
		## you can pass "px" through the function to signal intention to work with actual pixels
	#except:
		## but by defalt we work with dots
		#units = 'dot'
	#if units == 'dot':
		#x,y,dpi=kdehelper.screeninfo()
		#return round(value/(dpi*0.01))
	#else:
		#return value

# now, fixing another glitch - font size setting
# SK by default sets the size using setPIXELfontsize() instead of 
# setPOINTfontsize()
# this means that it is easier to align text to pixels in SK, but
# the whole thing will be tiny on hi-def monitors.
# In karambino, we will convert all text.size() calls into POINTS
# you can still use karambino.change*TextSize() directly, 
# using pixels, since those karamba functions are exposed.
def fontptpx(value, **kw):
	try:
		mode = kw['direction']
	except:
		mode = 'pt2px'
	if mode == 'pt2px':
		value = int(round(value * kdehelper.screeninfo()[2] / 72))
	elif mode == 'px2pt':
		value = int(round(value * 72 / kdehelper.screeninfo()[2]))
	return value

#######################################################
# basic meta classes = nestable object and action object.
# these are generic classes to designed to squeeze data and functions into of objects.
class SuperObjectClass:
	def __init__(self, widget=None, parent=None, action=None):
		'''
		globcfg - dict object we will be using for storing cross-object data.
			usually inherited up from parent or given manually if there is no parent.
		widget - mandatory for Canvas objects, helpful for Action objects
		parent - mandatory for Action objects, helpful for Canvas objects
			if only parent is given, we can get widget and globcfg out of parent.
		action - in case you want to override the default function
			example: a = SuperObjectClass(widget=12345, fn=mycustomfunction)
			Note: do not use () after function name.
			You can always do that later, by obj.action = your_custom_function
			activated by obj(your, custom, args, that, your_fn, accepts)
		'''
		self.optionsdb = {}
		self.objtype = 'SuperObjectClass'
		self.children = []

		if action:
			self.action = action
		else:
			self.action = self.__action__
		self.signals = {}

		if parent:
			self.parent = parent
		else:
			self.parent = parent
			self.widget = widget
			
		if self.parent:
			self.widget = self.parent.widget

	# this makes every object behave like a dictionary type.
	# useful for storage of options
	def __getitem__(self,item):
		try:
			return self.optionsdb[item]
		except:
			return None
	def __setitem__(self, item, value):
		self.optionsdb[item]=value

	def childadd(self, child):
		try:
			self.children.index(child)
			# if it works, we already have it there
		except:
			self.children.append(child)

	def __call__(self, *args, **kw):
		'''
		This is run when you do self()
		this function takes the value of self.action and executes the contents.
		
		self.action can be 2 things: a string with a command or a function object.
		
		args - tuple with all arguments passed to the function.
		Don't override this. Either:
		- override self.__action__ or
		- assign your custom function's object to self.action
		'''
		if type(self.action) == type(''): #string
			# this is very stupid
			try:
				exec 'retval = '+self.action
			except:
				exec self.action
				retval = None
		else:
			retval = self.action(*args, **kw)
		return retval

	def __action__(self, *args, **kw):
		'''
		This is a default action.
		Override me! or assign an OBJECT of your custom function to self.action
		'''
		return args, kw
	
	def signalset(self, *args, **kw):
		'''
		args - Tuple, where (name, function, arguments_list)
		name - mandatory element. If only name is given, this
		  signal is removed.
		function - can be a real function pointer or a string
		  which can be exec'ed.
		arguments_list - endless list of additional arguments.
		Examples:
		myimage.clicked.btn[1].signalset('my_custom_signal','print "asdf"')
		only button 1 will trigger the signal.
		myimage.clicked.signalset('my_custom_signal','print "asdf"')
		any button will trigger the signal.
		'''
		if args.__len__() > 1:
			name, fn, args = args[0], args[1], args[2:]
			self.signals.update({name:(fn,args,kw)})
			try:
				action = self.name
			except:
				action = None
			if action in ['clickables','hoverables','dropables']:
				# the thing is, we can assign many clicked() signals to many buttons.
				# and all assignments get into "clickables" or "hoverables" db under the same objid.
				# removing the clickablility for one button assignment 
				# should not remove the objid from the db as long as there are other assigned buttons.
				# clickables counter is the easiest way to see when it is time to remove 
				# ourselves from the clickables database.
				try:
					if self.parent.membership[action] < 0:
						self.parent.membership[action] = 1 # this is needed for cases when competing threads push this under 0.
					else:
						self.parent.membership[action] += 1
					# if this works, we are already in the clickables db and clickarea is attached.
				except:
					self.parent.membership = {action:1}
					cfgobj = getcfg(action)
					cfgobj[self.parent.objid] = self.parent
					# this DB is used in the helper.py module while detecting and sorting the clicks
					# self.parent.objid is SuperKaramba's widget/meter pointer/objectserialnumber
					# it is given to the processing function to know which widget was clicked.
					# if we see it in clickables DB, we use self.parent - the pointer to actual karambino superobject
					# (text, image, inputbox etc) and give it to signalemit() for manipulation.
					# so, if our object is a.clicked (.signalset())
					# (or a.clicked.btn[n] (.signalset()) with clicked.parent actually pointing to a)
					# we get the information for clicked's parent, which is - a
					try:
						clickable = not self.noclickarea
						# .noclickarea is usually present in "clickable" objects
						# but only with WHEEL BUTTONS assigned. In that case, it is not
						# nice to show the hand.
					except:
						clickable = True
						# by default, everything is clickable unless forbidden.
					if action == 'clickables' and clickable:
						try:
							attachClickArea(self.parent.widget, self.parent.objid)
							# in karamba only some widgets are clickable.
						except:
							# others will simply fail.
							pass
					if action == 'dropables':
						try:
							acceptDrops(self.parent.widget)
						except:
							pass
		else:
			# this means we only get the name of the signal as argument.
			# this means "set/change this signal to nothing = delete the signal."
			# or we got some trash as input.
			try:
				del self.signals[args[0]]
				try:
					action = self.parent.name
				except:
					action = None
				if action in ['clickables','hoverables','dropables']:
					try:
						# note, this may fail for many reasons.
						# regardless of the reason, we just extricate ourselves from 
						# dealing with this problem by resetting the counter
						# for the action in the except part bellow.
						self.parent.membership[action] -= 1
						cfgobj = getcfg(action)
						cfgobj[self.parent.objid] = self.parent
						if self.parent.membership[action] == 0:
							try:
								del cfgobj[self.parent.objid] # TODO: need to make sure this deletes the right thing.
							except:
								pass
					except:
						self.parent.membership = {action:0}
			except:
				pass

	def signalemit(self, *args, **kw_new):
		'''
		Trigered automatically only if obj.signalemit(args) is in the function.
		- If function stored in the signal database is a string - we expect that it
		contains all needed arguments. This way you can call standard functions.
		- If it is not string, we also look at the arguments.
		Args is a tuple (a,b,c,d,)
		We unpack it and merge with the argument dict stored in the signal
		This allows to pass a lot of arguments at flexible times.
		'''
		# plan:
		# check if any signals are assigned
		# run each. Trying to ensure speedy iteration through things.
		if self.signals and type(self.signals) == type({}):
			farm = getcfg('threadfarm') # this is our multi-threaded, general purpose heavylifter
			for signal in self.signals.keys():
				fn, args_stored, kw = self.signals[signal] # the contents of that should always be a tuple.
				if type(fn) == type('') or type(fn) == type(u''):
					# no arguments. only string with a command is supported.
					# user usually rolls the args into the fn string.
					exec fn
					# used for things like "print 'we are here'"
				else:
					# more complex case. Here we allow user to pass more arguments
					# we are going to join stored and new arg lists and expand that list
					args = (fn,)+args+args_stored
					kw.update(kw_new)
					#print "signal emit args, kw ", args, kw
					farm.add(*args, **kw)
#
###############################################################

def commonClass(widget=None, parent=None):
	'''
	OK, you cought me... it is not a class. Unfortunately making this a class
	is not possible as it would not be able to subclass from SuperObjectClass
	without messing up the __init__().
	We need here either widget OR parent.
	'''
	
	obj = SuperObjectClass(parent=parent, widget=widget)

	if obj.parent:
		obj.parent.childadd(obj)

	obj.action = 'self.objid'

	def clickable():
		'''
		Note, you don't need to do this by hand.
		when you assign signals to object clicked
		Ex: myimage.clicked.btn[1].signalset('my_custom_signal','print "asdf"')
		singnalset() automatically adds attachClickArea to the obj.
		'''
		cfgobj = getcfg('clickables')
		cfgobj[obj.objid] = obj
		attachClickArea(obj.widget, obj.objid)
	obj.clickable = SuperObjectClass(parent=obj, action=clickable)

	def clicked(button, x=None, y=None):
		'''
		button = [1,2,3,4,5] or [left, middle, right, wheelup, wheeldn]
		'''
		# print "button is ", button
		obj.clicked.signalemit(button=button)
		obj.clicked.btn[button].signalemit(button=button)
	obj.clicked = SuperObjectClass(parent=obj, action=clicked)
	obj.clicked.name = 'clickables'
	obj.clicked.btn = {}
	for i in [1,2,3,4,5]:
		obj.clicked.btn[i] = SuperObjectClass(parent=obj)
		# note, we are faking a parent here.
		# this in effect makes clicked and (clicked's) btn[n]
		# children of the obj and are on equal level when it comes
		# to signal processing, though for logical purposes
		# btn[] is a child of clicked
		# This trick allows .clicked AND its children to be processed
		# by the same signalset() function.
		obj.clicked.btn[i].name = 'clickables'
		obj.clicked.btn[i].action = obj.clicked.btn[i].signalemit
		if i in [4,5]:
			obj.clicked.btn[i].noclickarea = True
		else:
			obj.clicked.btn[i].noclickarea = False

	def itemDropped(dropText, x, y):
		obj.itemDropped.signalemit(droptext=dropText,x=x,y=y)
	obj.itemDropped = SuperObjectClass(parent=obj, action=itemDropped)
	obj.itemDropped.name = 'dropables'

	def mousein(x=0, y=0, button=None):
		'''
		if you connect to it, expect to get x,y,button arguments as 3 1st arguments in your fn.
		'''
		obj.mousein.signalemit(x=x,y=y,button=button)
	obj.mousein = SuperObjectClass(parent=obj, action=mousein)
	obj.mousein.name = 'hoverables'

	def mouseout(x=0, y=0, button=None):
		'''
		if you connect to it, expect to get x,y,button arguments as 3 1st arguments in your fn.
		'''
		obj.mouseout.signalemit(x=x,y=y,button=button)
	obj.mouseout = SuperObjectClass(parent=obj, action=mouseout)
	obj.mouseout.name = 'hoverables'

	def move(*args, **kw):
		'''
		list of ALL accepted arguments
		(x, y, selforigin, parentorigin, parentobj)
		Where:
		- x and y can be positive and negative and 0,0
		- selforigin and parentorigin are either strings or tuples
		  like 'ceter', or (.5 , .5) (the last one is ratio of width and height)
		  also note, the ratios CAN be negative and above 1 :) Try it.
		  Other values: center,n,s,w,e,nw,ne,sw,se
		
		While position() is an absolute-location mover,
		move() is a relative mover.
		If no qualifyer is passed, we are moving agains ourselves' old position.
		Otherwise, we are shifting against that object's parent's point of origin
		There is one more thing: you can specify the point of origin of the object as "center"
		and pass 0, 0 as move arguments, which would center this object
		against the parent.
		'''
		if obj.position.locked:
			pass
		else:
			opt = {}
			key = ['x','y','selforigin','parentorigin','parent']
			defvar = [0,0,(0,0),None,obj.parent] # 0,0 = upper left corner.
			# cleaning up.
			i = 0
			while i < args.__len__() and i < 5:
				opt[key[i]] = args[i]
				i += 1
			while i < 5:
				opt[key[i]] = defvar[i]
				i += 1
			vectx, vecty = obj.position()
			if not opt['parentorigin']:
				# we are just moving against our old position.
				newx, newy = vectx, vecty
			else:
				shorts = {
						'center':(0.5,0.5),
						'n':(0.5,0),
						's':(0.5,1),
						'w':(0,0.5),
						'e':(1,0.5),
						'nw':(0,0),
						'ne':(1,0),
						'sw':(0,1),
						'se':(1,1),
						}
				# we are moving against the parent't origin.
				# lets see what it is.
				newx, newy = opt['parent'].position()
				if opt['parentorigin'] != (0,0):
					# i.e. custom origin, not upper left corner
					if opt['parentorigin'] in shorts.keys():
						opt['parentorigin'] = shorts[opt['parentorigin']]
					w, h = opt['parent'].size()
					newx = newx+w*opt['parentorigin'][0]
					newy = newy+h*opt['parentorigin'][1]
				if opt['selforigin'] != (0,0):
					# i.e. custom origin, not upper left corner
					# we adjust it further
					if opt['selforigin'] in shorts.keys():
						opt['selforigin'] = shorts[opt['selforigin']]
					w, h = obj.size()
					newx = newx-w*opt['selforigin'][0]
					newy = newy-h*opt['selforigin'][1]
			
			newx = int(newx + opt['x'])
			newy = int(newy + opt['y'])
			vectx = newx - vectx
			vecty = newy - vecty
			
			obj.position(newx, newy)
			## position takes care of all the children
			#for item in obj.children:
			#	item.move(vectx, vecty)
			
			obj.move.signalemit(x=vectx,y=vecty)
			redraw(obj.widget, **kw)
	obj.move = SuperObjectClass(parent=obj, action=move)
	
	def __delete__(*args, **kw):
		'''
		This is a generic executer for delete functions.
		The actual function to the delete a particular element will
		be passed in as one of the arguments in **kw
		'''
		kw['fn'](obj.widget, obj.objid)
		obj.delete.signalemit()
		for item in obj.children:
			try:
				item.delete()
			except:
				pass
		redraw(obj.widget, **kw)
		try:
			del obj.parent.children[obj]
		except:
			pass
	obj.__delete__ = SuperObjectClass(parent=obj, action=__delete__)
	
	def __position__(*args, **kw):
		x,y = None, None
		if args.__len__() > 1:
			x, y = args[0], args[1]

		oldx, oldy = kw['fnout'](obj.widget, obj.objid)
		oldx = fromscreen(oldx,**kw)
		oldy = fromscreen(oldy,**kw)
		if x!=None and y!=None: # it is possible to have 0,0 as position.
			if not obj.position.locked:
				kw['fnin'](obj.widget, obj.objid, toscreen(x, **kw), toscreen(y,**kw))
				obj.position.signalemit(x=x,y=y)
				redraw(obj.widget, **kw)
				oldx = x - oldx
				oldy = y - oldy
				if oldx or oldy:
					for item in obj.children:
						try:
							item.move(oldx, oldy)
						except:
							pass
			return None
		else:
			return oldx, oldy
	obj.__position__ = SuperObjectClass(parent=obj, action=__position__)

	def __size__(*args, **kw):
		w,h = None, None
		if args.__len__() > 1:
			w, h = args[0], args[1]
		
		if w!=None and h!=None: # it is possible to have 0,0 as position.
			kw['fnin'](obj.widget, obj.objid, toscreen(w,**kw), toscreen(h,**kw))
			obj.size.signalemit(w=w,h=h)
			redraw(obj.widget, **kw)
			return None
		else:
			w,h = kw['fnout'](obj.widget, obj.objid)
			return fromscreen(w,**kw), fromscreen(h,**kw)
	obj.__size__ = SuperObjectClass(parent=obj, action=__size__)

	def visibility(*args, **kw):
		# kw['fn'] is a SuperKaramba function. Either [something]Show() or [something]Hide()
		# (obj.widget, obj.objid) are arguments.
		# The locking system is convoluted.
		# there are 2 lock systems, the "main" and "convenience"
		# the Main level is always a property of the object and lives in obj.visibility.locked = [true/false]
		# the Convenience is a lock bypass system and only operates on the level of function arguments.
		# (Do not confuse them with obj.visible = [true / False], which is just a flag.)
		#
		# By default there are NO locks active. This means if parents .hide(), ALL children also get .hide()
		# This may be a problem to some people, if they want some children to be always visible, while parent freely changes visibility states
		# Example of this is on_hover events, when a child (icon) may be a permanently visible object, that activates visibility of parent.
		# For that situation, you can set visibility state lock for each object INDIVIDUALLY.
		# use obj.visibility.locked = True
		#
		# In some instances you absolutely must hide children, and don't want to go through all of them and unlock them.
		try:
			i = kw['bypasslock']
		except:
			kw['bypasslock'] = False
		if (not obj.visibility.locked) or kw['bypasslock']:
			kw['fn'](obj.widget, obj.objid)
			obj.visible = kw['visistate']
			# cleaning up
			del kw['visistate'] # we will go into nested loop when will be processing chidren
			del kw['fn'] # and these will cause a conflict.
			if obj.visible: # i.e. was set to True by kw['visistate']
				obj.show.signalemit()
			else:
				obj.hide.signalemit()
		if (not obj.visibility.locked) or kw['bypasslock']:
			for item in obj.children:
				kw['redraw']=False
				if obj.visible: # i.e. was set to True by kw['visistate']
					item.show(**kw)
				else:
					item.hide(**kw)
		redraw(obj.widget, **kw)
	obj.visibility = SuperObjectClass(parent=obj, action=visibility)
	# TODO: put this into documentation. Come up with a better name.
	# if True, parent's hide() will pass on to this object
	obj.visibility.locked = False
	
	return obj

def textClass(text=None, x=None, y=None, w=None, h=None, parent=None, widget=None, objtype='text', obj=None):
	'''
	It's not really a class.
	Adds functionality of a text element to the element
	'''
	# obj.widget this is set up by CommonClass(widget) class assignment.
	
	if obj:
		pass
	else:
		obj = commonClass(widget=widget, parent=parent)

	obj.objtype = objtype # used for detection of capabilities
	# obj.name = None # may be used for indexing and referencing at parent level.

	if text == None:
		text = 'text object'
	if x == None or y == None:
		x = 0
		y = 0
	if w == None or h == None:
		w = 50
		h = 20
	
	obj.visible = True

	if obj.widget:
		obj.objid = createText(obj.widget, x, y, w, h, text)
	obj.timestamp = time()

	# **kw is a dict type containing system flags like "redraw after action"
	
	def delete(*args, **kw):
		obj.__delete__(fn=deleteText, **kw)
	obj.delete = SuperObjectClass(parent=obj, action=delete)
	obj.__del__ = obj.delete

	def position(*args, **kw):
		return obj.__position__(fnout=getTextPos, fnin=moveText, *args, **kw)
	obj.position = SuperObjectClass(parent=obj, action=position)
	obj.position.locked = False

	def size(*args, **kw):
		return obj.__size__(fnin=resizeText, fnout=getTextSize, *args, **kw)
	obj.size = SuperObjectClass(parent=obj, action=size)

	def show(*args, **kw):
		obj.visibility(fn=showText, visistate=True, **kw)
	obj.show = SuperObjectClass(parent=obj, action=show)
	
	def hide(*args, **kw):
		obj.visibility(fn=hideText, visistate=False, **kw)
	obj.hide = SuperObjectClass(parent=obj, action=hide)

	def text(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		text = None
		if args.__len__():
			text = args[0]

		if text != None:
			if type(text)!=type('') and type(text)!=type(u''):
				text = unicode(text)
			changeText(obj.widget, obj.objid, text)
			obj.text.signalemit(text=text)
			redraw(obj.widget, **kw)
		else:
			return getTextValue(obj.widget, obj.objid)
	obj.text = SuperObjectClass(parent=obj, action=text)
	
	def color(*args, **kw):
		'''
		red, green, blue, alpha (optional)
		TODO
		'''
		r,g,b = None, None, None
		if args.__len__() > 2:
			r,g,b = args[0], args[1],args[2]
		
		if r != None and g != None  and b != None:
			changeTextColor(obj.widget, obj.objid, r, g, b)
			obj.text.color.signalemit(r=r,g=g,b=b)
			redraw(obj.widget, **kw)
		else:
			return getTextColor(obj.widget, obj.objid)
	obj.text.color = SuperObjectClass(parent=obj.text, action=color)
	obj.text.color(0,0,0)

	def txsize(*args, **kw):
		'''
		size, name (disabled at this time)
		TODO
		'''
		if args:
			size = args[0]
		else:
			size = None

		if size: # or name:
			changeTextSize(obj.widget, obj.objid, fontptpx(size))
			obj.text.size.signalemit(size=size)
			redraw(obj.widget, **kw)
		else:
			return fontptpx(getTextFontSize(obj.widget, obj.objid), mode='px2pt')
	obj.text.size = SuperObjectClass(parent=obj.text, action=txsize)
	obj.text.size(14)

	def txfont(*args, **kw):
		'''
		size, name (disabled at this time)
		TODO
		'''
		if args:
			font = args[0]
		else:
			font = ''
		
		if font: # or name:
			changeTextFont(obj.widget, obj.objid, font)
			obj.text.font.signalemit(font=font)
			redraw(obj.widget, **kw)
		else:
			return getTextFont(obj.widget, obj.objid)
		#changeInputBoxFont(obj.widget, obj.objid, name)
	obj.text.font = SuperObjectClass(parent=obj.text, action=txfont)

	def align(*args, **kw):
		'''
		state = one of CENTER, LEFT, RIGHT.
		TODO
		'''
		state=None
		if args.__len__():
			state = args[0].upper()

		if state:
			#changeInputBoxFont(obj.widget, obj.objid, name)
			setTextAlign(obj.widget, obj.objid, state)
			obj.text.align.signalemit(align=state)
			redraw(obj.widget, **kw)
		else:
			return getTextAlign(obj.widget, obj.objid)
	obj.text.align = SuperObjectClass(parent=obj.text, action=align)
	obj.text.align('CENTER')
	
	# and now we are done with it.
	return obj

def richTextClass(text=None, x=None, y=None, w=None, h=None, parent=None, widget=None, objtype='richtext', obj=None):
	'''
	It's not really a class.
	Adds functionality of a richtext element to the element
	'''
	if obj:
		pass
	else:
		obj = commonClass(widget=widget, parent=parent)

	obj.objtype = objtype # used for detection of capabilities

	if text == None:
		text = 'richtext object'

	obj.visible = True

	# WARNING! NEED WIDGET NUMBER FOR EVERYTHING BELLOW
	if obj.widget:
		obj.objid = createRichText(obj.widget, text)
	obj.timestamp = time()
	# since this object does not take the size an position arguments upon creation,
	# we will use x,y and w,h lower in the code.
	
	def delete(*args, **kw):
		obj.__delete__(fn=deleteRichText, **kw)
	obj.delete = SuperObjectClass(parent=obj, action=delete)
	obj.__del__ = obj.delete

	def position(*args, **kw):
		return obj.__position__(fnout=getRichTextPos, fnin=moveRichText, *args, **kw)
	obj.position = SuperObjectClass(parent=obj, action=position)
	obj.position.locked = False

	def size(*args, **kw):
		return obj.__size__(fnin=resizeRichText, fnout=getRichTextSize, *args, **kw)
	obj.size = SuperObjectClass(parent=obj, action=size)

	def show(*args, **kw):
		obj.visibility(fn=showRichText, visistate=True, **kw)
	obj.show = SuperObjectClass(parent=obj, action=show)
	
	def hide(*args, **kw):
		obj.visibility(fn=hideRichText, visistate=False, **kw)
	obj.hide = SuperObjectClass(parent=obj, action=hide)

	def text(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		text = None
		if args.__len__():
			text = args[0]
		
		if text != None:
			if type(text)!=type('') and type(text)!=type(u''):
				text = unicode(text)
			changeRichText(obj.widget, obj.objid, text)
			obj.text.signalemit(text=text)
			redraw(obj.widget, **kw)
		else:
			return getRichTextValue(obj.widget, obj.objid)
	obj.text = SuperObjectClass(parent=obj, action=text)

	# Now is the time to process x,y and w,h if given.
	if x!=None and y!=None:
		obj.position(x,y)
	if w!=None and h!=None:
		obj.size(w,h)
	
	# and now we are done with it.
	return obj

def imageClass(path=None, x=None, y=None, w=None, h=None, parent=None, widget=None, objtype='image', obj=None):
	'''
	It's not really a class.
	Adds functionality of a text element to the element
	'''
	# obj.widget this is set up by CommonClass(widget) class assignment.
	if obj:
		pass
	else:
		obj = commonClass(widget=widget, parent=parent)

	obj.objtype = objtype # used for detection of capabilities
	# obj.name = None # may be used for indexing and referencing at parent level.

	if path == None:
		path = 'null.png'
	if x == None or y == None:
		x = 0
		y = 0

	obj.visible = True

	# WARNING! NEED WIDGET NUMBER FOR EVERYTHING BELLOW
	if obj.widget:
		obj.objid = createImage(obj.widget, x, y, path)
		if w != None and h != None:
			resizeImage(obj.widget, obj.objid, w, h)
	obj.timestamp = time()

	def delete(*args, **kw):
		obj.__delete__(fn=deleteImage, **kw)
	obj.delete = SuperObjectClass(parent=obj, action=delete)
	obj.__del__ = obj.delete

	def position(*args, **kw):
		return obj.__position__(fnout=getImagePos, fnin=moveImage, *args, **kw)
	obj.position = SuperObjectClass(parent=obj, action=position)
	obj.position.locked = False

	def size(*args, **kw):
		return obj.__size__(fnin=resizeImage, fnout=getImageSize, *args, **kw)
	obj.size = SuperObjectClass(parent=obj, action=size)

	def show(*args, **kw):
		obj.visibility(fn=showImage, visistate=True, **kw)
	obj.show = SuperObjectClass(parent=obj, action=show)
	
	def hide(*args, **kw):
		obj.visibility(fn=hideImage,visistate=False,  **kw)
	obj.hide = SuperObjectClass(parent=obj, action=hide)

	def path(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		path = None
		if args.__len__():
			path = args[0]
		
		if path != None:
			setImagePath(obj.widget, obj.objid, path)
			obj.path.signalemit(path=path)
			redraw(obj.widget, **kw)
		else:
			return getImagePath(obj.widget, obj.objid)
	obj.path = SuperObjectClass(parent=obj, action=path)

	def rotation(*args, **kw):
		'''
		degrees against 12 O'Clock, always. Not relative old position.
		'''
		deg=None
		if args.__len__():
			deg = args[0]

		if deg != None:
			obj.lastangle = deg
			rotateImage(obj.widget, obj.objid, deg)
			obj.rotation.signalemit(deg=deg)
			redraw(obj.widget, **kw)
		else:
			return obj.lastangle
	obj.rotation = SuperObjectClass(parent=obj, action=rotation)
	obj.lastangle = 0

	def tip(*args, **kw):
		'''
		text
		'''
		text = None
		if args.__len__():
			text = args[0]
			addImageTooltip(obj.widget, obj.objid, text)
		redraw(obj.widget, **kw)
	obj.tip = SuperObjectClass(parent=obj, action=tip)

	# and now we are done with it.
	return obj

def addFrame(parent, paths):
	'''
	A helper function intended for imageClass mostly, but can be used for any sizable object.
	This is a group of 8 images used to draw frames and backgrounds
	of arbitrary size with zero image distortion of the corners.
	No management of frame position is really needed. Each frame part is
	linked with signals to the parent object and adjusts by itself.
	Takes a LIST of 8 paths as path argument.
	path = [nw,n,ne,w,e,sw,s,se] You can omitt the center image.
	parent object is mandatory. All alignment of the frame is done around the parent.
	NOTE! Does not work with all rotated images.
	'''
	
	# creating the frame.
	parent.frame = {}
	for path,part in zip(paths,['nw','n','ne','w','e','sw','s','se']): # would be 8 paths.
		parent.frame[part] = imageClass(path,parent=parent)

	def refreshframe(*args, **kw):
		'''
		We assume here that the frame style/width itself did not change.
		When triggered by parent's position() moves the frame pieces.
		Expects x and y to be passed in **kw, but prefer to use parent.position()
		'''
		try:
			w,h = parent.size()
		except:
			w,h = None, None
		try:
			oldw,oldh = parent.frame['oldw'],parent.frame['oldh']
		except:
			oldw,oldh = None, None

		if True: #not (w,h) in [(None, None),(oldw,oldh)]:
			# we will be here only if x,y,w,h changed and are real numbers
			move = False # just a flag for our internal logic. Try to avoid unneeded manipulations.
			nw_w, nw_h = parent.frame['nw'].size() # size of upper left piece.
			se_w, se_h = parent.frame['se'].size() # size of lower right piece.
			# if (oldnw_w,oldnw_h,oldse_w,oldse_h) != (nw_w, nw_h, se_w, se_h):
			if (oldw,oldh) != (w,h):
				parent.frame['n'].size(w,nw_h)
				parent.frame['s'].size(w,se_h)
				parent.frame['w'].size(nw_w,h)
				parent.frame['e'].size(se_w,h)
				# since the alignment point overlaps by default, we always offset by 1 point
				parent.frame['n'].move(0,0,'sw','nw') # align to parent's top
				parent.frame['s'].move(0,0,'nw','sw') # align to parent's bottom
				parent.frame['w'].move(0,0,'ne','nw') # align to parent's left side
				parent.frame['e'].move(0,0,'nw','ne') # align to parent's right side
				parent.frame['nw'].move(0,0,'se','nw')
				parent.frame['ne'].move(0,0,'sw','ne')
				parent.frame['sw'].move(0,0,'ne','sw')
				parent.frame['se'].move(0,0,'nw','se')
			parent.frame['oldw'],parent.frame['oldh'] = w,h
			# NOTE: this is the only part of data that could be useful
			# to have access to from outside.
			# the numbers are: width, height of top left corner, and width, height of lower right corner.
			# these 4 numbers will perfectly describe any asymmetrical rectangular frame.
			# as they translate to left width, top height, right width and bottom height of the frame.
			parent.frame['size'] = (nw_w,nw_h,se_w,se_h)

	refreshframe()
	# parent.position.signalset('refreshframe',refreshframe) # this is done auto.
	parent.size.signalset('refreshframe',refreshframe)
	try:
		parent.path.signalset('refreshframe',refreshframe)
	except:
		pass

def inputClass(text=None, x=None, y=None, w=None, h=None, parent=None, widget=None, objtype='input', obj=None):
	'''
	It's not really a class.
	Adds functionality of an input element to the object
	'''
	# obj.widget this is set up by CommonClass(widget) class assignment.
	
	if obj:
		pass
	else:
		obj = commonClass(widget=widget, parent=parent)

	obj.objtype = objtype # used for detection of capabilities

	if text == None:
		text = 'input object'
	if x == None or y == None:
		x = 0
		y = 0
	if w == None or h == None:
		w = 70
		h = 20

	obj.visible = True

	# WARNING! NEED WIDGET NUMBER FOR EVERYTHING BELLOW
	if obj.widget:
		obj.objid = createInputBox(obj.widget, x, y, w, h, text)
		obj.timestamp = time()
		# custom tweaks
		cfgobj = getcfg('clickables')
		cfgobj[obj.objid] = obj

	def delete(*args, **kw):
		obj.__delete__(fn=deleteInputBox, **kw)
	obj.delete = SuperObjectClass(parent=obj, action=delete)
	obj.__del__ = obj.delete

	def position(*args, **kw):
		return obj.__position__(fnout=getInputBoxPos, fnin=moveInputBox, *args, **kw)
	obj.position = SuperObjectClass(parent=obj, action=position)
	obj.position.locked = False

	def size(*args, **kw):
		return obj.__size__(fnin=resizeInputBox, fnout=getInputBoxSize, *args, **kw)
	obj.size = SuperObjectClass(parent=obj, action=size)

	def show(*args, **kw):
		obj.visibility(fn=showInputBox, visistate=True, **kw)
	obj.show = SuperObjectClass(parent=obj, action=show)
	
	def hide(*args, **kw):
		obj.visibility(fn=hideInputBox, visistate=False, **kw)
	obj.hide = SuperObjectClass(parent=obj, action=hide)

	def text(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		text = None
		if args.__len__():
			text = args[0]
		
		if text != None:
			if type(text)!=type('') and type(text)!=type(u''):
				text = unicode(text)
			changeInputBox(obj.widget, obj.objid, text)
			obj.text.signalemit(text=text)
			redraw(obj.widget, **kw)
		else:
			return getInputBoxValue(obj.widget, obj.objid)
	obj.text = SuperObjectClass(parent=obj, action=text)

	def color(*args, **kw):
		'''
		red, green, blue, alpha (optional)
		TODO
		'''
		r,g,b = None, None, None
		if args.__len__() > 2:
			r,g,b = args[0], args[1],args[2]
			
		if r != None and g != None  and b != None:
			changeInputBoxFontColor(obj.widget, obj.objid, r, g, b)
			changeInputBoxBackgroundColor(obj.widget, obj.objid, 255-r, 255-g, 255-b)
			changeInputBoxFrameColor(obj.widget, obj.objid, 255-r, 255-g, 255-b)
			obj.text.color.signalemit(r=r,g=g,b=b)
			redraw(obj.widget, **kw)
		else:
			return getInputBoxFontColor(obj.widget, obj.objid)
	obj.text.color = SuperObjectClass(parent=obj.text, action=color)
	obj.text.color(0,0,0)

	def txsize(*args, **kw):
		'''
		size, name (disabled at this time)
		TODO
		'''
		if args:
			size = args[0]
		else:
			size = None

		if size: # or name:
			changeInputBoxFontSize(obj.widget, obj.objid, size)
			obj.text.size.signalemit(size=size)
			redraw(obj.widget, **kw)
		else:
			return getInputBoxFontSize(obj.widget, obj.objid)
		#changeInputBoxFont(obj.widget, obj.objid, name)
	obj.text.size = SuperObjectClass(parent=obj.text, action=txsize)
	obj.text.size(14)

	def txfont(*args, **kw):
		'''
		size, name (disabled at this time)
		TODO
		'''
		if args:
			font = args[0]
		else:
			font = ''
		
		if font: # or name:
			changeInputBoxFont(obj.widget, obj.objid, font)
			obj.text.font.signalemit(font=font)
			redraw(obj.widget, **kw)
		else:
			return getInputBoxFont(obj.widget, obj.objid)
		#changeInputBoxFont(obj.widget, obj.objid, name)
	obj.text.font = SuperObjectClass(parent=obj.text, action=txfont)

	def submit(*args, **kw):
		'''
		This is triggered only when an Enter button is pressed 1st time.
		Consecutive pressed are ignored.
		'''
		obj.submit.signalemit(text=getInputBoxValue(obj.widget, obj.objid))
	obj.submit = SuperObjectClass(parent=obj, action=submit)

	obj.keypressed = obj.clicked

	def escape(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		obj.escape.signalemit()
	obj.escape = SuperObjectClass(parent=obj, action=escape)

	def focus(*args, **kw):
		'''
		If nothing is passed - return status - True if active.
		True or False passed in activate / deactivate inputbox.
		'''
		try:
			state = args[0]
		except:
			state = True
		if state:
			setInputFocus(obj.widget, obj.objid)
			obj.focus.signalemit()
			redraw(obj.widget, **kw)
		else:
			clearInputFocus(obj.widget, obj.objid)
			redraw(obj.widget, **kw)
	obj.focus = SuperObjectClass(parent=obj, action=focus)

	# and now we are done with it.
	return obj

def fancyInputClass(text=None, x=None, y=None, w=None, h=None, parent=None, widget=None, objtype='fancyinput', obj=None):
	'''
	Substitute for real inputbox SK element.
	Behaves largely like a text class
	additional items of interest:
	self.textcursor = some unicode char.
	self.guessdirection(text) = guesses the directionality of the script.
	  it only returns -num (RTL), 0(uncertain), or +num (LRT)
	'''
	if obj:
		pass
	else:
		obj = commonClass(widget=widget, parent=parent)

	# So, all the methods of the CommonClass come with it:
	# clickable, clicked, itemDropped, mousein, mouseout, move, visibility
	# we need to point to correct __delete__, __position__, __size__
	
	# we need this guy 1st because it should be under the main text	
	obj.tip = textClass('click and type', x, y, w, h, parent=obj)
	# now, the main text
	obj = textClass('', x, y, w, h, parent, widget, objtype, obj)
	# now, out off-screen real inputbox
	obj.inputobj = inputClass(text='', x=0, y=-30, w=1, h=1, parent=obj)
	obj.inputobj.position.locked = True
	
	# by default, this is a small under-letter up-arrow 2038
	# set this to the cursor you want.
	# 2022 - ball, 25CF - larger ball 
	obj.textcursor = u'\u2022'
	obj.text.lastdirtest = '' # used for LTR and RTL detection bypassing

	#obj.text.align.signalset("copycat", obj.tip.text.align)
	#obj.text.color.signalset("copycat", obj.tip.text.color)
	obj.text.size.signalset("copycat", obj.tip.text.size)
	obj.text.font.signalset("copycat", obj.tip.text.font)
	#obj.position.signalset("copycat", obj.tip.position)
	obj.size.signalset("copycat", obj.tip.size)
	obj.show.signalset("copycat", obj.tip.show)
	obj.hide.signalset("copycat", obj.tip.hide)
	obj.delete.signalset("copycat", obj.tip.delete)
	obj.delete.signalset("copycat", obj.inputobj.delete)

	obj.submit = obj.inputobj.submit
	obj.keypressed = obj.inputobj.keypressed
	
	def escape(*args, **kw):
		obj.text(obj.inputobj.text())
		obj.inputobj.focus(False)
		if len(obj.inputobj.text()) == 0:
			obj.tip.show()
		obj.escape.signalemit()
	obj.escape = SuperObjectClass(parent=obj, action=escape)
	obj.inputobj.escape.signalset("hidingbox", obj.escape)

	def directionchanged(*args, **kw):
		if args:
			text = args[0]
		else:
			test = obj.text()
		if (text[:obj.text.lastdirtest.__len__()] == obj.text.lastdirtest) and obj.text.lastdirtest:
			r = 0
		else:
			# we are here ether because obj.text.lastdirtest is empty or 
			# because text in inputbox is not equal to obj.text.lastdirtest
			#
			# we are looking for 1st non-European-digit char.
			# note, there is a tremendous amount of holes left in this
			# algorythm. Basically, I assume that if the char
			# in place is 1) neither number, 2) nor in specific range of 
			# arabic, hebrew, syriac, thaan char codes, it's a LTR script.
			# what may happen is some non-leter chars outside of 32-63 range
			# (i.e. curly brackets) are entered and they trigger LTR,
			# even if they are followed by RTL script.
			# obj.text.direction has 3 states:
			# *  0 = no value assigned
			# * +n = ltr, where n is the char number at which ltr-specific
			#        characters start. Useful for strings that start with numbers
			# * -n = rtl, see +n explanation for n value.
			i = 0
			obj.text.lastdirtest = ''
			r = 0
			while i < text.__len__():
				if (31 < ord(text[i]) < 64): # symbol or number
					pass
				elif (1424 < ord(text[i]) < 1938): # rtl script
					obj.text.lastdirtest = text[:i+1]
					r = -1
					break
				else:
					obj.text.lastdirtest = text[:i+1]
					r = +1
					break
				i += 1
		return r
	obj.directionchanged = directionchanged

	def stealtext(*args, **kw):
		'''
		taking text from input and assigning it to text object
		'''
		newtext = obj.inputobj.text()
		direction = obj.directionchanged(newtext)
		# accomodating LTR and RTL language scripts
		if direction > 0:
			obj.text.align('RIGHT')
		elif direction < 0:
			obj.text.align('LEFT')
		# hiding tooltip on small entries. 0 is the len at which tip is displayed.
		#if newtext.__len__() == 0 and obj.tip.visible == False:
			#obj.tip.show()
		#elif newtext.__len__() > 0 and obj.tip.visible == True:
			#obj.tip.hide()
		obj.text(obj.inputobj.text()+obj.textcursor)
	obj.inputobj.clicked.signalset('pushtotype', stealtext)

	# turning text into a button with arbitrary action.
	def onfocus(*args, **kw):
		# if there is any text in the inputbox, we add that
		# to the label and add a cursor in the end.
		# if there is no text - just the cursor.
		obj.tip.hide()
		obj.text(obj.inputobj.text()+obj.textcursor)
		obj.inputobj.focus()
	obj.clicked.btn[1].signalset('focusinput', onfocus)

	# for ease of use, we will override the text() function.
	# assigning of text will still go to label,
	# but getting of text will always be from inputbox
	def text(*args, **kw):
		'''
		Both, sets AND gets the text.
		If you pass an argument, we are a Setter
		If you pass no argument, we are a getter
		'''
		text = None
		if args.__len__():
			text = args[0]

		if text != None:
			if type(text)!=type('') and type(text)!=type(u''):
				text = unicode(text) # that's in case a number was sent
			changeText(obj.widget, obj.objid, text)
			obj.text.signalemit(text=text)
			redraw(obj.widget, **kw)
		else:
			return obj.inputobj.text()
	# it was already made SuperObjectClass before.
	obj.text.action = text

	return obj

def tileListClass(parent=None, widget=None, objtype='tilelist', obj=None):
	'''
	Implements a basic listview widget, composed of vertically stacked tiles.
	Each tile is built by one user-provided function and populated by another user-fn.
	parent or widget - Indirect and direct means to know widget numbr. See commonClass for info.
	obj - you may want to pre-create and populate the widget with elements to 
	 ensure that they stay on the bottom. Passing this in allows to inherit that
	 and build on that, instead of creating a new object.
	'''
	if obj:
		pass
	else:
		obj = textClass('',widget=widget, parent=parent)
	# normally, a background to a list should be an image.
	# because we can only assign clickarea to images or text, and image looks better as background
	# but, here, because we give the user a chance to assign tilebuild and tilepopulate functions
	# we have to create the skeleton and stop, giving the user a chance to assign the custom functions to this object.
	# buildtile, populatetile and initialize bellow are default _examples_. They show you
	# how to do your own fn's and where to park them.

	# These are mandatory variables. If you override initialize(), you need to set this up by hand.
	obj.data = []
	obj.container = commonClass(parent=obj)
	obj.container.toptile = 0 # used for scrolling, don't touch.
	obj.container.tiles = []
	obj.container.width = 70 # sample 100x3 rows widget
	obj.container.visibletiles = 3 # sample 100x3 rows widget
	obj.container.tileheight = 30 # sample 100x3 rows widget

	def __buildtile__(parent, tilenum, **kw):
		'''
		This function is responsible for CREATION of an object that will be a tile.
		It can be any sizable "surface", like an image or a text widget,
		on top of which you can stack more elements.
		If you are using karambino tileListClass's initialize() fn, you MUST implement
		this function and assign it to obj.buildtile
		parent - reference to the container that will contain all the tiles.
		You don't have to think about positioning of this tile, the container will do it for you.
		All you do here is build the tile with elements. Don't bother with setting correct
		values to things here. Just add empty element.
		tilenum - number generated by initialize(), refers to tile's place in visible line at the time of building the listview.
		Do not mess with it, unless you have to. May be used for slternating background or other things.
		obj - reference to tileListClass object instance.
		'''
		# you can use global to get your settings into here.
		# the following variables of parent may also help you:
		# parent.width = width of the tileList container.
		# parent.visibletiles = 
		# parent.tileheight = 
		tile = textClass('',w=parent.width,h=30,parent=parent) # we are using empty text widget as a tile container.
		tile.title = textClass('',w=parent.width,h=26,parent=tile) # this is our example of nested data carier.
		tile.title.text.color(255,255,255,redraw=False)
		tile.title.move(0,2,'nw','nw',redraw=False) # offsetting 2 pixels from the top of parent.
		return tile
	obj.buildtile = __buildtile__ # note, if you park your function here, it will still be in the scope of your globals. Use that.

	def __populatetile__(tile, dataunit, obj=None, **kw):
		'''
		This function MUST be implemented and parked at obj.populatetile, with the minimum support of tile and data args.
		This "method" will be called by the scrolling functions.
		tile - pointer to tile object.
		dataunit - pointer to data object from which to collect the pieces and populate the tile.
		These are the only two things this fn will be given when called by other internal fns
		obj - reference to tileListClass object. You can pull the common data out of that.
		'''
		tile.title.text(dataunit,redraw=False)
		# you don't need to redraw for the whole function because right after it, 
		# we move the tile and redraw anyway.
	obj.populatetile = __populatetile__

	def scrolldown(*args, **kw):
		'''
		obj - reference to tileListClass instance object.
		'''
		# tiles go up, data item numbers increase.
		# 1. Moves all tiles but top one up
		# 2. moves the top tile tile down.
		# 3. inserts the next item from que onto that last tile.
		if (obj.container.tiles[obj.container.toptile].datanum + obj.container.visibletiles) < (obj.data.__len__()):
			b = obj.container
			for i in range(b.toptile+1, b.toptile+b.visibletiles):
				# if the topmost tile is [1], this produces the following range [2,3]
				if i >= b.visibletiles: # this will result in the following range [2,0]
					i = i - b.visibletiles
				b.tiles[i].move(0, -(b.tileheight))
			# repopulating the item
			b.tiles[b.toptile].datanum = b.tiles[b.toptile].datanum + b.visibletiles
			obj.populatetile(b.tiles[b.toptile], obj.data[b.tiles[b.toptile].datanum])
			# moving the file
			b.tiles[b.toptile].move(0,b.tileheight*(b.visibletiles-1))
			b.toptile += 1
			if b.toptile >= b.visibletiles:
				b.toptile = b.toptile - b.visibletiles
			obj.scrolldown.signalemit()
	obj.scrolldown = SuperObjectClass(parent=obj, action=scrolldown)
	
	def scrollup(*args, **kw):
		'''
		obj - reference to tileListClass instance object.
		'''
		# the plan:
		# tiles go down, data item numbers decrease.
		# 1. Moves all tiles but the bottom one down
		# 2. moves the bottom tile tile all the way up.
		# 3. repopulate the new top tile with new data.
		# moves all tiles, but bottom one down. Moved the bottom tile all the way to top.
		if obj.container.tiles[obj.container.toptile].datanum > 0: # stops working when data unit # 0 is on top.
			b = obj.container
			for i in range(b.toptile, b.toptile+b.visibletiles-1):
				# note, now we are dealing with Tile numbers, not data unit numbers.
				# we resue the tiles by moving them up and down.
				# if the topmost tile is [1] and 3 tiles [0,1,2] are visible, this produces the following range [1,2]
				# if the topmost tile is [2], [2,3] is produced.
				# [3] is out-of range index, so we wrap it back to zero+:
				if i >= b.visibletiles:
					i = i - b.visibletiles # this will result in the following range [2,0]
				b.tiles[i].move(0, b.tileheight) # moving down exactly the height of one tile.
			# changing tile index and calculating the data unit number based on OLD top tile's data unit number.
			newitem = b.tiles[b.toptile].datanum - 1 # capturing the data unit number from OLD top tile and decreasing it to arrive to new top data unit number.
			b.toptile -= 1 # tiles "move" down, tile serial number decreases.
			if b.toptile < 0:
				b.toptile = b.toptile + b.visibletiles
			b.tiles[b.toptile].datanum = newitem # now, that tile is still on the bottom, but was marked with new data unit number.
			# all we have left is repopulate it with data and move up.
			obj.populatetile(b.tiles[b.toptile], obj.data[newitem])
			b.tiles[b.toptile].move(0,0, 'nw','nw')
			obj.scrollup.signalemit()
	obj.scrollup = SuperObjectClass(parent=obj, action=scrollup)

	def refresh(datanum=0):
		'''
		This fn only reloads the values on top of present tile.
		If you need to change the dimensions of the vidget, delete(), initialize()
		
		NOTE: This function expects that obj.data WAS ALREADY CHANGED.
		'''
		# in case you are wondering, we are inheriting obj from the parent function. It is the tilelist object.
		dl = len(obj.data)
		for i in range(obj.container.visibletiles):
			obj.container.tiles[i].show(redraw = False) # we need this, or else the move will not work.
			obj.container.tiles[i].move(0, obj.container.tileheight*i, 'nw','nw')
			if i < dl:
				obj.populatetile(obj.container.tiles[i], obj.data[datanum], obj)
				obj.container.tiles[i].datanum = datanum
				datanum += 1
			else:
				obj.container.tiles[i].hide(redraw = False)
		redraw(obj.widget)
	obj.refresh = SuperObjectClass(parent=obj, action=refresh)

	# somewhere here, find a way to draw a shorter list of tiles if data list is short.
	def __initialize(obj, data=None, x=None, y=None, w=None, h=None, tileheight=None):
		'''
		Draws listview widget.
		Each tile is built by one user-provided function and populated by another user-fn.
		Make sure you either assigned the obj.
		data (optional) - an array containing identically-organized sets of your data for each tile.
		ex: [['pic1.png','title1'],['pic2.png','title2'],['pic3.png','title3']]
		x, y (optional) - absolute location of the tileList widget
		w (optional) - width of the main list body. This will the the width of all tiles.
		Note, other custom elelments, like fancy-shmancy scroll bar, may hang outside of this zone.
		h (optional) - height of main list body IN MULTIPLES OF VISIBLE TILES.
		ex: h=4 means that the height of the widget will be 4x(Height of one tile)
		tileheight (optional) - height of individual tile. tileh x h = height of the tilelist.
		  (Note, these are optional ONLY IF you already set them up directly on obj.container already,
		   or you are OK with the default values.)
		obj - I don't have to know this one. This function is a child of the parent object
		and can always just ask for obj.parent, or something like that. 
		But, since I am showing you how to make custom init fn for tilelist widget, they
		will be functions assigned to, but EXTERNAL to this object. So, they must know what the parent object is.
		As a result, the activation will look a bit stupid, like this:
		my_fancy_list.initialize(my_fancy_list)   ... but, that is the easiest way to achieve functionality.
		Consider obj.initialize just a parking spot for the fn. You don't even have to park there to run your initialize fn.
		'''
		# Things you must do if you override this function.
		# 1. Set up the following variables:
		#    obj.container.width, obj.container.visibletiles, obj.container.tileheight
		# 2. Assign data to obj.data array (list)
		# 3. Make the obj.container a complete, sizable object, by upgrading it to 
		#     an image or text class. (make sure to position that object in space.
		# 4. Create obj.container.tiles[] and populate it with tile objects by
		#    calling obj.buildtile() and obj.populatetile()
		if type(data)!=None:
			obj.data = data
		if type(w)==type(1):
			obj.container.width = w
		if type(h)==type(1):
			obj.container.visibletiles = h
		if type(tileheight)==type(1):
			obj.container.tileheight = tileheight
		obj.container = textClass('',
							 x=x,
							 y=y,
							 w=obj.container.width,
							 h=obj.container.visibletiles*obj.container.tileheight,
							 parent=obj,
							 obj=obj.container) # if you override this fn, you can make it an image as well.
		dl = len(obj.data)
		for i in range(obj.container.visibletiles):
			obj.container.tiles.append(obj.buildtile(obj.container, i))
			obj.container.tiles[i].tilenum = i # this is the serial number ot the tile. We reuse the tiles and this number does not relate to serial number of displayed data. This number does not change.
			obj.container.tiles[i].datanum = i # since this is a beginning of the list, we use the same num. Later, in scrollup/down() we will change this number.
			# this is here for cases when we have less data than requested tiles.
			#print 'new position', obj.container.tiles[i].objtype, obj.container.tileheight*i
			obj.container.tiles[i].move(0, obj.container.tileheight*i, 'nw','nw')
			if i < dl:
				obj.populatetile(obj.container.tiles[i], obj.data[i], obj)
			else:
				obj.container.tiles[i].hide()
		obj.container.clicked.btn[4].signalset('scrollup', scrollup, obj)
		obj.container.clicked.btn[5].signalset('scrolldown', scrolldown, obj)
	obj.initialize = __initialize

	return obj
